/*
 * File: HdaCodec.c
 *
 * Copyright (c) 2018 John Davis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

//#include "HdaCodec.h"
#include "HdaCodecComponentName.h"
//#include <IndustryStandard/HdaCodec.h>
#include <Library/HdaModels.h>

EFI_STATUS
EFIAPI
HdaCodecProbeWidget(
                    IN HDA_WIDGET_DEV *HdaWidget)
{
  //DEBUG((DEBUG_INFO, "HdaCodecProbeWidget(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo = HdaWidget->FuncGroup->HdaCodecDev->HdaIo;
  UINT32 Response = 0;
  UINT8 ConnectionListThresh;
  UINT8 AmpInCount;
  
  // Get widget capabilities.
  Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_WIDGET_CAPS), &HdaWidget->Capabilities);
  if (EFI_ERROR(Status))
    return Status;
  HdaWidget->Type = HDA_PARAMETER_WIDGET_CAPS_TYPE(HdaWidget->Capabilities);
  HdaWidget->AmpOverride = HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_AMP_OVERRIDE;
  //DEBUG((DEBUG_INFO, "Widget @ 0x%X type: 0x%X\n", HdaWidget->NodeId, HdaWidget->Type));
  //DEBUG((DEBUG_INFO, "Widget @ 0x%X capabilities: 0x%X\n", HdaWidget->NodeId, HdaWidget->Capabilities));
  
  // Get default unsolicitation.
  if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_UNSOL_CAPABLE) {
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_UNSOL_RESPONSE, 0), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->DefaultUnSol = (UINT8)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X unsolicitation: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultUnSol));
  }
  
  // Get connections.
  if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_CONN_LIST) {
    // Get connection list length.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_CONN_LIST_LENGTH), &HdaWidget->ConnectionListLength);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->ConnectionCount = HDA_PARAMETER_CONN_LIST_LENGTH_LEN(HdaWidget->ConnectionListLength);
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X connection list length: 0x%X\n", HdaWidget->NodeId, HdaWidget->ConnectionListLength));
    
    // Get connections.
    HdaWidget->Connections = AllocateZeroPool(sizeof(UINT16) * HdaWidget->ConnectionCount);
    if (HdaWidget->Connections == NULL)
      return EFI_OUT_OF_RESOURCES;
    ConnectionListThresh = (HdaWidget->ConnectionListLength & HDA_PARAMETER_CONN_LIST_LENGTH_LONG) ? 2 : 4;
    for (UINT8 c = 0; c < HdaWidget->ConnectionCount; c++) {
      // Do we need to get entries?
      if (!(c % ConnectionListThresh)) {
        // Get connection entries.
        Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                    HDA_CODEC_VERB(HDA_VERB_GET_CONN_LIST_ENTRY, c), &Response);
        if (EFI_ERROR(Status))
          return Status;
      }
      
      // Populate entry list.
      if ((HdaWidget->ConnectionListLength & HDA_PARAMETER_CONN_LIST_LENGTH_LONG))
        HdaWidget->Connections[c] = HDA_VERB_GET_CONN_LIST_ENTRY_LONG(Response, c % 2);
      else
        HdaWidget->Connections[c] = HDA_VERB_GET_CONN_LIST_ENTRY_SHORT(Response, c % 4);
    }
  }
  
  // Print connections.
  //DEBUG((DEBUG_INFO, "Widget @ 0x%X connections (%u):", HdaWidget->NodeId, HdaWidget->ConnectionCount));
  //for (UINT8 c = 0; c < HdaWidget->ConnectionCount; c++)
  //DEBUG((DEBUG_INFO, " 0x%X", HdaWidget->Connections[c]));
  //DEBUG((DEBUG_INFO, "\n"));
  
  // Does the widget support power management?
  if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_POWER_CNTRL) {
    // Get supported power states.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUPPORTED_POWER_STATES), &HdaWidget->SupportedPowerStates);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X supported power states: 0x%X\n", HdaWidget->NodeId, HdaWidget->SupportedPowerStates));
    
    // Get default power state.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_POWER_STATE, 0), &HdaWidget->DefaultPowerState);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X power state: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultPowerState));
  }
  
  // Do we have input amps?
  if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_IN_AMP) {
    // Get input amp capabilities.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_AMP_CAPS_INPUT), &HdaWidget->AmpInCapabilities);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X input amp capabilities: 0x%X\n", HdaWidget->NodeId, HdaWidget->AmpInCapabilities));
    
    // Determine number of input amps and allocate arrays.
    AmpInCount = HdaWidget->ConnectionCount;
    if (AmpInCount < 1)
      AmpInCount = 1;
    HdaWidget->AmpInLeftDefaultGainMute = AllocateZeroPool(sizeof(UINT8) * AmpInCount);
    HdaWidget->AmpInRightDefaultGainMute = AllocateZeroPool(sizeof(UINT8) * AmpInCount);
    if ((HdaWidget->AmpInLeftDefaultGainMute == NULL) || (HdaWidget->AmpInRightDefaultGainMute == NULL))
      return EFI_OUT_OF_RESOURCES;
    
    // Get default gain/mute for input amps.
    for (UINT8 i = 0; i < AmpInCount; i++) {
      // Get left.
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                  HDA_CODEC_VERB(HDA_VERB_GET_AMP_GAIN_MUTE,
                                                 HDA_VERB_GET_AMP_GAIN_MUTE_PAYLOAD(i, TRUE, FALSE)), &Response);
      if (EFI_ERROR(Status))
        return Status;
      HdaWidget->AmpInLeftDefaultGainMute[i] = (UINT8)Response;
      
      // Get right.
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                  HDA_CODEC_VERB(HDA_VERB_GET_AMP_GAIN_MUTE,
                                                 HDA_VERB_GET_AMP_GAIN_MUTE_PAYLOAD(i, FALSE, FALSE)), &Response);
      if (EFI_ERROR(Status))
        return Status;
      HdaWidget->AmpInRightDefaultGainMute[i] = (UINT8)Response;
      //DEBUG((DEBUG_INFO, "Widget @ 0x%X input amp %u defaults: 0x%X 0x%X\n", HdaWidget->NodeId, i,
      //    HdaWidget->AmpInLeftDefaultGainMute[i], HdaWidget->AmpInRightDefaultGainMute[i]));
    }
  }
  
  // Do we have an output amp?
  if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_OUT_AMP) {
    // Get output amp capabilities.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_AMP_CAPS_OUTPUT), &HdaWidget->AmpOutCapabilities);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X output amp capabilities: 0x%X\n", HdaWidget->NodeId, HdaWidget->AmpOutCapabilities));
    
    // Get left.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_AMP_GAIN_MUTE,
                                               HDA_VERB_GET_AMP_GAIN_MUTE_PAYLOAD(0, TRUE, TRUE)), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->AmpOutLeftDefaultGainMute = (UINT8)Response;
    
    // Get right.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_AMP_GAIN_MUTE,
                                               HDA_VERB_GET_AMP_GAIN_MUTE_PAYLOAD(0, FALSE, TRUE)), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->AmpOutRightDefaultGainMute = (UINT8)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X output amp defaults: 0x%X 0x%X\n", HdaWidget->NodeId,
    //    HdaWidget->AmpOutLeftDefaultGainMute, HdaWidget->AmpOutRightDefaultGainMute));
  }
  
  // Is the widget an Input or Output?
  if (HdaWidget->Type == HDA_WIDGET_TYPE_INPUT || HdaWidget->Type == HDA_WIDGET_TYPE_OUTPUT) {
    if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_FORMAT_OVERRIDE) {
      // Get supported PCM sizes/rates.
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                  HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUPPORTED_PCM_SIZE_RATES), &HdaWidget->SupportedPcmRates);
      if (EFI_ERROR(Status))
        return Status;
      //DEBUG((DEBUG_INFO, "Widget @ 0x%X supported PCM sizes/rates: 0x%X\n", HdaWidget->NodeId, HdaWidget->SupportedPcmRates));
      
      // Get supported stream formats.
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                  HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUPPORTED_STREAM_FORMATS), &HdaWidget->SupportedFormats);
      if (EFI_ERROR(Status))
        return Status;
      //DEBUG((DEBUG_INFO, "Widget @ 0x%X supported formats: 0x%X\n", HdaWidget->NodeId, HdaWidget->SupportedFormats));
    }
    
    // Get default converter format.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_CONVERTER_FORMAT, 0), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->DefaultConvFormat = (UINT16)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X default format: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultConvFormat));
    
    // Get default converter stream/channel.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_CONVERTER_STREAM_CHANNEL, 0), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->DefaultConvStreamChannel = (UINT8)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X default stream/channel: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultConvStreamChannel));
    
    // Get default converter channel count.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_CONVERTER_CHANNEL_COUNT, 0), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->DefaultConvChannelCount = (UINT8)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X default channel count: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultConvChannelCount));
  } else if (HdaWidget->Type == HDA_WIDGET_TYPE_PIN_COMPLEX) { // Is the widget a Pin Complex?
                                                               // Get pin capabilities.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_PIN_CAPS), &HdaWidget->PinCapabilities);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X pin capabilities: 0x%X\n", HdaWidget->NodeId, HdaWidget->PinCapabilities));
    
    // Get default EAPD.
    if (HdaWidget->PinCapabilities & HDA_PARAMETER_PIN_CAPS_EAPD) {
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                  HDA_CODEC_VERB(HDA_VERB_GET_EAPD_BTL_ENABLE, 0), &Response);
      if (EFI_ERROR(Status))
        return Status;
      HdaWidget->DefaultEapd = (UINT8)Response;
      HdaWidget->DefaultEapd &= 0x7;
      HdaWidget->DefaultEapd |= HDA_EAPD_BTL_ENABLE_EAPD;
      //DEBUG((DEBUG_INFO, "Widget @ 0x%X EAPD: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultEapd));
    }
    
    // Get default pin control.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PIN_WIDGET_CONTROL, 0), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->DefaultPinControl = (UINT8)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X default pin control: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultPinControl));
    
    // Get default pin configuration.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_CONFIGURATION_DEFAULT, 0), &HdaWidget->DefaultConfiguration);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X default pin configuration: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultConfiguration));
  } else if (HdaWidget->Type == HDA_WIDGET_TYPE_VOLUME_KNOB) { // Is the widget a Volume Knob?
                                                               // Get volume knob capabilities.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_VOLUME_KNOB_CAPS), &HdaWidget->VolumeCapabilities);
    if (EFI_ERROR(Status))
      return Status;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X volume knob capabilities: 0x%X\n", HdaWidget->NodeId, HdaWidget->VolumeCapabilities));
    
    // Get default volume.
    Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId,
                                HDA_CODEC_VERB(HDA_VERB_GET_VOLUME_KNOB, 0), &Response);
    if (EFI_ERROR(Status))
      return Status;
    HdaWidget->DefaultVolume = (UINT8)Response;
    //DEBUG((DEBUG_INFO, "Widget @ 0x%X default volume: 0x%X\n", HdaWidget->NodeId, HdaWidget->DefaultVolume));
  }
  
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaCodecProbeFuncGroup(
                       IN HDA_FUNC_GROUP *FuncGroup) {
  //DEBUG((DEBUG_INFO, "HdaCodecProbeFuncGroup(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo = FuncGroup->HdaCodecDev->HdaIo;
  UINT32 Response;
  
  UINT8 WidgetStart;
  UINT8 WidgetEnd;
  UINT8 WidgetCount;
  HDA_WIDGET_DEV *HdaWidget;
  HDA_WIDGET_DEV *HdaConnectedWidget;
  
  // Get function group type.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_FUNC_GROUP_TYPE), &Response);
  if (EFI_ERROR(Status))
    return Status;
  FuncGroup->Type = HDA_PARAMETER_FUNC_GROUP_TYPE_NODETYPE(Response);
  FuncGroup->UnsolCapable = (Response & HDA_PARAMETER_FUNC_GROUP_TYPE_UNSOL) != 0;
  
  // Determine if function group is an audio one. If not, we cannot support it.
  DEBUG((DEBUG_INFO, "Function group @ 0x%X is of type 0x%X\n", FuncGroup->NodeId, FuncGroup->Type));
  if (FuncGroup->Type != HDA_FUNC_GROUP_TYPE_AUDIO)
    return EFI_UNSUPPORTED;
  
  // Get function group capabilities.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_FUNC_GROUP_CAPS), &FuncGroup->Capabilities);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X capabilities: 0x%X\n", FuncGroup->NodeId, FuncGroup->Capabilities));
  
  // Get default supported PCM sizes/rates.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUPPORTED_PCM_SIZE_RATES), &FuncGroup->SupportedPcmRates);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X supported PCM sizes/rates: 0x%X\n", FuncGroup->NodeId, FuncGroup->SupportedPcmRates));
  
  // Get default supported stream formats.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUPPORTED_STREAM_FORMATS), &FuncGroup->SupportedFormats);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X supported formats: 0x%X\n", FuncGroup->NodeId, FuncGroup->SupportedFormats));
  
  // Get default input amp capabilities.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_AMP_CAPS_INPUT), &FuncGroup->AmpInCapabilities);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X input amp capabilities: 0x%X\n", FuncGroup->NodeId, FuncGroup->AmpInCapabilities));
  
  // Get default output amp capabilities.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_AMP_CAPS_OUTPUT), &FuncGroup->AmpOutCapabilities);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X output amp capabilities: 0x%X\n", FuncGroup->NodeId, FuncGroup->AmpOutCapabilities));
  
  // Get supported power states.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUPPORTED_POWER_STATES), &FuncGroup->SupportedPowerStates);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X supported power states: 0x%X\n", FuncGroup->NodeId, FuncGroup->SupportedPowerStates));
  
  // Get GPIO capabilities.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_GPIO_COUNT), &FuncGroup->GpioCapabilities);
  if (EFI_ERROR(Status))
    return Status;
  //DEBUG((DEBUG_INFO, "Function group @ 0x%X GPIO capabilities: 0x%X\n", FuncGroup->NodeId, FuncGroup->GpioCapabilities));
  
  // Get number of widgets in function group.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUBNODE_COUNT), &Response);
  if (EFI_ERROR(Status))
    return Status;
  WidgetStart = HDA_PARAMETER_SUBNODE_COUNT_START(Response);
  WidgetCount = HDA_PARAMETER_SUBNODE_COUNT_TOTAL(Response);
  WidgetEnd = WidgetStart + WidgetCount - 1;
  DEBUG((DEBUG_INFO, "Function group @ 0x%X contains %u widgets, start @ 0x%X, end @ 0x%X\n",
         FuncGroup->NodeId, WidgetCount, WidgetStart, WidgetEnd));
  
  // Power up.
  Status = HdaIo->SendCommand(HdaIo, FuncGroup->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_POWER_STATE, 0), &Response);
  ASSERT_EFI_ERROR(Status);
  
  // Ensure there are widgets.
  if (WidgetCount == 0)
    return EFI_UNSUPPORTED;
  
  // Allocate space for widgets.
  FuncGroup->Widgets = AllocateZeroPool(sizeof(HDA_WIDGET_DEV) * WidgetCount);
  if (FuncGroup->Widgets == NULL)
    return EFI_OUT_OF_RESOURCES;
  FuncGroup->WidgetsCount = WidgetCount;
  
  // Probe widgets.
  DEBUG((DEBUG_INFO, "HdaCodecProbeFuncGroup(): probing widgets\n"));
  for (UINT8 w = 0; w < WidgetCount; w++) {
    // Get widget.
    HdaWidget = FuncGroup->Widgets + w;
    
    // Probe widget.
    HdaWidget->FuncGroup = FuncGroup;
    HdaWidget->NodeId = WidgetStart + w;
    Status = HdaCodecProbeWidget(HdaWidget);
    ASSERT_EFI_ERROR(Status);
    
    // Power up.
    if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_POWER_CNTRL) {
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_POWER_STATE, 0), &Response);
      ASSERT_EFI_ERROR(Status);
    }
  }
  
  // Probe widget connections.
  DEBUG((DEBUG_INFO, "HdaCodecProbeFuncGroup(): probing widget connections\n"));
  for (UINT8 w = 0; w < WidgetCount; w++) {
    // Get widget.
    HdaWidget = FuncGroup->Widgets + w;
    
    // Get connections.
    if (HdaWidget->ConnectionCount > 0) {
      // Allocate array of widget pointers.
      HdaWidget->WidgetConnections = AllocateZeroPool(sizeof(HDA_WIDGET_DEV*) * HdaWidget->ConnectionCount);
      if (HdaWidget->WidgetConnections == NULL)
        return EFI_OUT_OF_RESOURCES;
      
      // Populate array.
      for (UINT8 c = 0; c < HdaWidget->ConnectionCount; c++) {
        // Get widget index.
        // This can be gotten using the node ID of the connection minus our starting node ID.
        UINT16 WidgetIndex = HdaWidget->Connections[c] - WidgetStart;
        if (WidgetIndex < 0) {
          DEBUG((DEBUG_INFO, "Widget @ 0x%X error connection to index %u (0x%X) is invalid\n", WidgetIndex, HdaWidget->Connections[c]));
          continue;
        }
        
        // Save pointer to widget.
        HdaConnectedWidget = FuncGroup->Widgets + WidgetIndex;
        //DEBUG((DEBUG_INFO, "Widget @ 0x%X found connection to index %u (0x%X, type 0x%X)\n",
        //    HdaWidget->NodeId, WidgetIndex, HdaConnectedWidget->NodeId, HdaConnectedWidget->Type));
        HdaWidget->WidgetConnections[c] = HdaConnectedWidget;
      }
    }
  }
  
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaCodecProbeCodec(
                   IN HDA_CODEC_DEV *HdaCodecDev)
{
  //DEBUG((DEBUG_INFO, "HdaCodecProbeCodec(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo = HdaCodecDev->HdaIo;
  UINT32 Response;
  UINT8 FuncStart;
  UINT8 FuncEnd;
  UINT8 FuncCount;
  
  // Get vendor and device ID.
  Status = HdaIo->SendCommand(HdaIo, HDA_NID_ROOT,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_VENDOR_ID), &HdaCodecDev->VendorId);
  if (EFI_ERROR(Status))
    return Status;
  DEBUG((DEBUG_INFO, "Codec ID: 0x%X:0x%X\n", HDA_PARAMETER_VENDOR_ID_VEN(HdaCodecDev->VendorId), HDA_PARAMETER_VENDOR_ID_DEV(HdaCodecDev->VendorId)));
  
  // Get revision ID.
  Status = HdaIo->SendCommand(HdaIo, HDA_NID_ROOT,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_REVISION_ID), &HdaCodecDev->RevisionId);
  if (EFI_ERROR(Status))
    return Status;
  
  HdaCodecGetName(HdaCodecDev->VendorId, (UINT16)HdaCodecDev->RevisionId, &HdaCodecDev->Name);
  
  // Get function group count.
  Status = HdaIo->SendCommand(HdaIo, HDA_NID_ROOT,
                              HDA_CODEC_VERB(HDA_VERB_GET_PARAMETER, HDA_PARAMETER_SUBNODE_COUNT), &Response);
  if (EFI_ERROR(Status))
    return Status;
  FuncStart = HDA_PARAMETER_SUBNODE_COUNT_START(Response);
  FuncCount = HDA_PARAMETER_SUBNODE_COUNT_TOTAL(Response);
  FuncEnd = FuncStart + FuncCount - 1;
  DEBUG((DEBUG_INFO, "Codec contains %u function groups, start @ 0x%X, end @ 0x%X\n", FuncCount, FuncStart, FuncEnd));
  
  // Ensure there are functions.
  if (FuncCount == 0)
    return EFI_UNSUPPORTED;
  
  // Allocate space for function groups.
  HdaCodecDev->FuncGroups = AllocateZeroPool(sizeof(HDA_FUNC_GROUP) * FuncCount);
  if (HdaCodecDev->FuncGroups == NULL)
    return EFI_OUT_OF_RESOURCES;
  HdaCodecDev->FuncGroupsCount = FuncCount;
  HdaCodecDev->AudioFuncGroup = NULL;
  
  // Probe functions.
  for (UINT8 i = 0; i < FuncCount; i++) {
    HdaCodecDev->FuncGroups[i].HdaCodecDev = HdaCodecDev;
    HdaCodecDev->FuncGroups[i].NodeId = FuncStart + i;
    Status = HdaCodecProbeFuncGroup(HdaCodecDev->FuncGroups + i);
    if (!(EFI_ERROR(Status)) && (HdaCodecDev->AudioFuncGroup == NULL))
      HdaCodecDev->AudioFuncGroup = HdaCodecDev->FuncGroups + i;
  }
  
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaCodecFindUpstreamOutput(
                           IN HDA_WIDGET_DEV *HdaWidget,
                           IN UINT8 Level)
{
  EFI_STATUS Status;
  HDA_WIDGET_DEV *HdaConnectedWidget;
  
  //DEBUG((DEBUG_INFO, "HdaCodecFindUpstreamOutput(): start\n"));
  
  // If level is above 15, we may have entered an infinite loop so just give up.
  if (Level > 15) {
    return EFI_ABORTED;
  }
  
  // Go through connections and check for Output widgets.
  for (UINT8 c = 0; c < HdaWidget->ConnectionCount; c++) {
    // Get connected widget.
    HdaConnectedWidget = HdaWidget->WidgetConnections[c];
    for (UINT8 i = 0; i <= Level; i++) {
      DEBUG((DEBUG_INFO, "  "));
    }
    DEBUG((DEBUG_INFO, "Widget @ 0x%X (type 0x%X)\n", HdaConnectedWidget->NodeId, HdaConnectedWidget->Type));
    
    // If this is an Output, we are done.
    if (HdaConnectedWidget->Type == HDA_WIDGET_TYPE_OUTPUT) {
      HdaWidget->UpstreamWidget = HdaConnectedWidget;
      HdaWidget->UpstreamIndex = c;
      return EFI_SUCCESS;
    }
    
    // Check connections of connected widget.
    // If a success status is returned, that means an Output widget was found and we are done.
    Status = HdaCodecFindUpstreamOutput(HdaConnectedWidget, Level + 1);
    if (Status == EFI_SUCCESS) {
      HdaWidget->UpstreamWidget = HdaConnectedWidget;
      HdaWidget->UpstreamIndex = c;
      return EFI_SUCCESS;
    }
  }
  
  // We didn't find an Output if we got here (probably zero connections).
  return EFI_NOT_FOUND;
}

EFI_STATUS
EFIAPI
HdaCodecParsePorts(
                   IN HDA_CODEC_DEV *HdaCodecDev)
{
  //DEBUG((DEBUG_INFO, "HdaCodecParsePorts(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo = HdaCodecDev->HdaIo;
  HDA_FUNC_GROUP *HdaFuncGroup;
  HDA_WIDGET_DEV *HdaWidget;
  UINT8 DefaultDeviceType;
  UINT32 Response;
  
  // Loop through each function group.
  for (UINT8 f = 0; f < HdaCodecDev->FuncGroupsCount; f++) {
    // Get function group.
    HdaFuncGroup = HdaCodecDev->FuncGroups + f;
    
    // Loop through each widget.
    for (UINT8 w = 0; w < HdaFuncGroup->WidgetsCount; w++) {
      // Get widget.
      HdaWidget = HdaFuncGroup->Widgets + w;
      
      // Is the widget a pin complex? If not, ignore it.
      // If this is a pin complex but it has no connection to a port, also ignore it.
      // If the default association for the pin complex is zero, also ignore it.
      if ((HdaWidget->Type != HDA_WIDGET_TYPE_PIN_COMPLEX) ||
          (HDA_VERB_GET_CONFIGURATION_DEFAULT_PORT_CONN(HdaWidget->DefaultConfiguration) == HDA_CONFIG_DEFAULT_PORT_CONN_NONE) ||
          (HDA_VERB_GET_CONFIGURATION_DEFAULT_ASSOCIATION(HdaWidget->DefaultConfiguration) == 0))
        continue;
      
      // Determine if port is an output based on the device type.
      DefaultDeviceType = HDA_VERB_GET_CONFIGURATION_DEFAULT_DEVICE(HdaWidget->DefaultConfiguration);
      if ((DefaultDeviceType == HDA_CONFIG_DEFAULT_DEVICE_LINE_OUT) || (DefaultDeviceType == HDA_CONFIG_DEFAULT_DEVICE_SPEAKER) ||
          (DefaultDeviceType == HDA_CONFIG_DEFAULT_DEVICE_HEADPHONE_OUT) || (DefaultDeviceType == HDA_CONFIG_DEFAULT_DEVICE_SPDIF_OUT) ||
          (DefaultDeviceType == HDA_CONFIG_DEFAULT_DEVICE_OTHER_DIGITAL_OUT)) {
        
        // Try to get upstream output.
        DEBUG((DEBUG_INFO, "Port widget @ 0x%X is an output (pin defaults 0x%X)\n", HdaWidget->NodeId, HdaWidget->DefaultConfiguration));
        Status = HdaCodecFindUpstreamOutput(HdaWidget, 0);
        if (EFI_ERROR(Status))
          continue;
        
        // Enable output amp.
        Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_PIN_WIDGET_CONTROL,
                                                                             HDA_VERB_SET_PIN_WIDGET_CONTROL_PAYLOAD(0, FALSE, FALSE, TRUE, FALSE)), &Response);
        if (EFI_ERROR(Status))
          continue;
        
        // If EAPD is present, enable.
        if (HdaWidget->PinCapabilities & HDA_PARAMETER_PIN_CAPS_EAPD) {
          // Get current EAPD setting.
          Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_GET_EAPD_BTL_ENABLE, 0), &Response);
          if (EFI_ERROR(Status))
            return Status;
          
          // If the EAPD is not set, set it.
          if (!(Response & HDA_EAPD_BTL_ENABLE_EAPD)) {
            Response |= HDA_EAPD_BTL_ENABLE_EAPD;
            Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_EAPD_BTL_ENABLE,
                                                                                 (UINT8)Response), &Response);
            if (EFI_ERROR(Status))
              return Status;
          }
        }
        
        // If the output amp supports muting, unmute.
        if (HdaWidget->AmpOutCapabilities & HDA_PARAMETER_AMP_CAPS_MUTE) {
          UINT8 offset = HDA_PARAMETER_AMP_CAPS_OFFSET(HdaWidget->AmpOutCapabilities); // TODO set volume.
          
          // If there are no overriden amp capabilities, check function group.
          if (!(HdaWidget->AmpOverride))
            offset = HDA_PARAMETER_AMP_CAPS_OFFSET(HdaWidget->FuncGroup->AmpOutCapabilities);
          
          // Unmute amp.
          Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_AMP_GAIN_MUTE,
                                                                               HDA_VERB_SET_AMP_GAIN_MUTE_PAYLOAD(0, offset, FALSE, TRUE, TRUE, FALSE, TRUE)), &Response);
          if (EFI_ERROR(Status))
            return Status;
        }
        
        // Reallocate output array.
        HdaCodecDev->OutputPorts = ReallocatePool(sizeof(HDA_WIDGET_DEV*) * HdaCodecDev->OutputPortsCount, sizeof(HDA_WIDGET_DEV*) * (HdaCodecDev->OutputPortsCount + 1), HdaCodecDev->OutputPorts);
        if (HdaCodecDev->OutputPorts == NULL)
          return EFI_OUT_OF_RESOURCES;
        HdaCodecDev->OutputPortsCount++;
        
        // Add widget to output array.
        HdaCodecDev->OutputPorts[HdaCodecDev->OutputPortsCount - 1] = HdaWidget;
      }
    }
  }
  
  // Wait 1000ms for all widgets to fully come on.
  gBS->Stall(MS_TO_MICROSECOND(1000));
  
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaCodecInstallProtocols(
                         IN HDA_CODEC_DEV *HdaCodecDev)
{
  DEBUG((DEBUG_INFO, "HdaCodecInstallProtocols(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  HDA_CODEC_INFO_PRIVATE_DATA *HdaCodecInfoData;
  AUDIO_IO_PRIVATE_DATA *AudioIoData;
  
  // Allocate space for protocol data.
  HdaCodecInfoData = AllocateZeroPool(sizeof(HDA_CODEC_INFO_PRIVATE_DATA));
  AudioIoData = AllocateZeroPool(sizeof(AUDIO_IO_PRIVATE_DATA));
  if ((HdaCodecInfoData == NULL) || (AudioIoData == NULL)) {
    Status = EFI_OUT_OF_RESOURCES;
    goto FREE_POOLS;
  }
  
  // Populate info protocol data.
  HdaCodecInfoData->Signature = HDA_CODEC_PRIVATE_DATA_SIGNATURE;
  HdaCodecInfoData->HdaCodecDev = HdaCodecDev;
  HdaCodecInfoData->HdaCodecInfo.GetName = HdaCodecInfoGetCodecName;
  HdaCodecInfoData->HdaCodecInfo.GetVendorId = HdaCodecInfoGetVendorId;
  HdaCodecInfoData->HdaCodecInfo.GetRevisionId = HdaCodecInfoGetRevisionId;
  HdaCodecInfoData->HdaCodecInfo.GetAudioFuncId = HdaCodecInfoGetAudioFuncId;
  HdaCodecInfoData->HdaCodecInfo.GetDefaultRatesFormats = HdaCodecInfoGetDefaultRatesFormats;
  HdaCodecInfoData->HdaCodecInfo.GetDefaultAmpCaps = HdaCodecInfoGetDefaultAmpCaps;
  HdaCodecInfoData->HdaCodecInfo.GetWidgets = HdaCodecInfoGetWidgets;
  HdaCodecInfoData->HdaCodecInfo.FreeWidgetsBuffer = HdaCodecInfoFreeWidgetsBuffer;
  HdaCodecDev->HdaCodecInfoData = HdaCodecInfoData;
  
  // Populate I/O protocol data.
  AudioIoData->Signature = HDA_CODEC_PRIVATE_DATA_SIGNATURE;
  AudioIoData->HdaCodecDev = HdaCodecDev;
  AudioIoData->AudioIo.GetOutputs = HdaCodecAudioIoGetOutputs;
  AudioIoData->AudioIo.SetupPlayback = HdaCodecAudioIoSetupPlayback;
  AudioIoData->AudioIo.StartPlayback = HdaCodecAudioIoStartPlayback;
  AudioIoData->AudioIo.StartPlaybackAsync = HdaCodecAudioIoStartPlaybackAsync;
  AudioIoData->AudioIo.StopPlayback = HdaCodecAudioIoStopPlayback;
  HdaCodecDev->AudioIoData = AudioIoData;
  
  // Install protocols.
  Status = gBS->InstallMultipleProtocolInterfaces(&HdaCodecDev->ControllerHandle,
                                                  &gEfiHdaCodecInfoProtocolGuid, &HdaCodecInfoData->HdaCodecInfo,
                                                  &gEfiAudioIoProtocolGuid, &AudioIoData->AudioIo,
                                                  &gEfiCallerIdGuid, HdaCodecDev, NULL);
  if (EFI_ERROR(Status))
    goto FREE_POOLS;
  return EFI_SUCCESS;
  
FREE_POOLS:
  if (HdaCodecInfoData != NULL)
    FreePool(HdaCodecInfoData);
  if (AudioIoData != NULL)
    FreePool(AudioIoData);
  return Status;
}

EFI_STATUS
EFIAPI
HdaCodecGetOutputDac(
                     IN  HDA_WIDGET_DEV *HdaWidget,
                     OUT HDA_WIDGET_DEV **HdaOutputWidget)
{
  DEBUG((DEBUG_INFO, "HdaCodecGetOutputDac(): start\n"));
  
  // Check that parameters are valid.
  if ((HdaWidget == NULL) || (HdaOutputWidget == NULL))
    return EFI_INVALID_PARAMETER;
  
  // Crawl through widget path looking for output DAC.
  while (HdaWidget != NULL) {
    // Is this widget an output DAC?
    if (HdaWidget->Type == HDA_WIDGET_TYPE_OUTPUT) {
      *HdaOutputWidget = HdaWidget;
      return EFI_SUCCESS;
    }
    
    // Move to upstream widget.
    HdaWidget = HdaWidget->UpstreamWidget;
  }
  
  // If we get here, we couldn't find the DAC.
  return EFI_NOT_FOUND;
}

EFI_STATUS
EFIAPI
HdaCodecGetSupportedPcmRates(
                             IN  HDA_WIDGET_DEV *HdaPinWidget,
                             OUT UINT32 *SupportedRates)
{
  EFI_STATUS Status;
  HDA_WIDGET_DEV *HdaOutputWidget;
  
  //    DEBUG((DEBUG_INFO, "HdaCodecGetSupportedPcmRates(): start\n"));
  
  // Check that parameters are valid.
  if ((HdaPinWidget == NULL) || (SupportedRates == NULL))
    return EFI_INVALID_PARAMETER;
  
  // Get output DAC widget.
  Status = HdaCodecGetOutputDac(HdaPinWidget, &HdaOutputWidget);
  if (EFI_ERROR(Status))
    return Status;
  
  // Does the widget specify format info?
  if (HdaOutputWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_FORMAT_OVERRIDE) {
    // Check widget for PCM support.
    if (!(HdaOutputWidget->SupportedFormats & HDA_PARAMETER_SUPPORTED_STREAM_FORMATS_PCM))
      return EFI_UNSUPPORTED;
    *SupportedRates = HdaOutputWidget->SupportedPcmRates;
  } else {
    // Check function group for PCM support.
    if (!(HdaOutputWidget->FuncGroup->SupportedFormats & HDA_PARAMETER_SUPPORTED_STREAM_FORMATS_PCM))
      return EFI_UNSUPPORTED;
    *SupportedRates = HdaOutputWidget->FuncGroup->SupportedPcmRates;
  }
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaCodecDisableWidgetPath(
                          IN HDA_WIDGET_DEV *HdaWidget)
{
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo = HdaWidget->FuncGroup->HdaCodecDev->HdaIo;
  UINT32 Response = 0;
  
  //DEBUG((DEBUG_INFO, "HdaCodecDisableWidgetPath(): start\n"));
  
  // Check if widget is valid.
  if (HdaWidget == NULL)
    return EFI_INVALID_PARAMETER;
  
  // Crawl through widget path.
  while (HdaWidget != NULL) {
    // If Output, disable stream.
    if (HdaWidget->Type == HDA_WIDGET_TYPE_OUTPUT) {
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_CONVERTER_STREAM_CHANNEL,
                                                                           HDA_VERB_SET_CONVERTER_STREAM_PAYLOAD(0, 0)), &Response);
      if (EFI_ERROR(Status))
        return Status;
    }
    
    //  If widget is a pin complex, disable output.
    if (HdaWidget->Type == HDA_WIDGET_TYPE_PIN_COMPLEX) {
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_PIN_WIDGET_CONTROL,
                                                                           HDA_VERB_SET_PIN_WIDGET_CONTROL_PAYLOAD(0, FALSE, FALSE, FALSE, FALSE)), &Response);
      if (EFI_ERROR(Status))
        return Status;
    }
    
    // Move to upstream widget.
    HdaWidget = HdaWidget->UpstreamWidget;
  }
  
  // Path disabled.
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HdaCodecEnableWidgetPath(
                         IN HDA_WIDGET_DEV *HdaWidget,
                         IN UINT8 Volume,
                         IN UINT8 StreamId,
                         IN UINT16 StreamFormat)
{
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo = HdaWidget->FuncGroup->HdaCodecDev->HdaIo;
  UINT32 Response = 0;
  
  //DEBUG((DEBUG_INFO, "HdaCodecEnableWidgetPath(): start\n"));
  
  // Check if widget is valid.
  if ((HdaWidget == NULL) || (Volume > EFI_AUDIO_IO_PROTOCOL_MAX_VOLUME))
    return EFI_INVALID_PARAMETER;
  
  // Crawl through widget path.
  while (HdaWidget != NULL) {
    DEBUG((DEBUG_INFO, "Widget @ 0x%X setting up\n", HdaWidget->NodeId));
    
    // If pin complex, set as output.
    if (HdaWidget->Type == HDA_WIDGET_TYPE_PIN_COMPLEX) {
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_PIN_WIDGET_CONTROL,
                                                                           HDA_VERB_SET_PIN_WIDGET_CONTROL_PAYLOAD(0, FALSE, FALSE, TRUE, FALSE)), &Response);
      if (EFI_ERROR(Status))
        return Status;
      
      // If EAPD, enable.
      if (HdaWidget->PinCapabilities & HDA_PARAMETER_PIN_CAPS_EAPD) {
        // Get current EAPD setting.
        Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_GET_EAPD_BTL_ENABLE, 0), &Response);
        if (EFI_ERROR(Status))
          return Status;
        
        // If the EAPD is not set, set it.
        if (!(Response & HDA_EAPD_BTL_ENABLE_EAPD)) {
          Response |= HDA_EAPD_BTL_ENABLE_EAPD;
          Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_EAPD_BTL_ENABLE,
                                                                               (UINT8)Response), &Response);
          if (EFI_ERROR(Status))
            return Status;
        }
      }
    }
    
    // If this is a digital widget, enable digital output.
    if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_DIGITAL) {
      // Enable digital output.
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_DIGITAL_CONV_CONTROL1,
                                                                           HDA_DIGITAL_CONV_CONTROL_DIGEN), &Response);
      if (EFI_ERROR(Status))
        return Status;
      
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_ASP_MAPPING,
                                                                           0x00), &Response);
      if (EFI_ERROR(Status))
        return Status;
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_ASP_MAPPING,
                                                                           0x11), &Response);
      if (EFI_ERROR(Status))
        return Status;
    }
    
    // If there is an output amp, unmute.
    if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_OUT_AMP) {
      UINT8 offset = HDA_PARAMETER_AMP_CAPS_OFFSET(HdaWidget->AmpOutCapabilities); // TODO set volume.
      
      // If there are no overriden amp capabilities, check function group.
      if (!(HdaWidget->AmpOverride))
        offset = HDA_PARAMETER_AMP_CAPS_OFFSET(HdaWidget->FuncGroup->AmpOutCapabilities);
      
      // Calculate offset.
      offset = (offset * Volume) / EFI_AUDIO_IO_PROTOCOL_MAX_VOLUME;
      DEBUG((DEBUG_INFO, "HdaCodecEnableWidgetPath(): Amp out offset 0x%X\n", offset));
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_AMP_GAIN_MUTE,
                                                                           HDA_VERB_SET_AMP_GAIN_MUTE_PAYLOAD(0, offset, FALSE, TRUE, TRUE, FALSE, TRUE)), &Response);
      if (EFI_ERROR(Status))
        return Status;
    }
    
    // If there are input amps, mute all but the upstream.
    if (HdaWidget->Capabilities & HDA_PARAMETER_WIDGET_CAPS_IN_AMP) {
      DEBUG((DEBUG_INFO, "Widget @ 0x%X in amp\n", HdaWidget->NodeId));
      for (UINT8 c = 0; c < HdaWidget->ConnectionCount; c++) {
        if (HdaWidget->UpstreamIndex == c) {
          UINT8 offset = HDA_PARAMETER_AMP_CAPS_OFFSET(HdaWidget->AmpInCapabilities);
          // If there are no overriden amp capabilities, check function group.
          if (!(HdaWidget->AmpOverride))
            offset = HDA_PARAMETER_AMP_CAPS_OFFSET(HdaWidget->FuncGroup->AmpInCapabilities);
          Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_AMP_GAIN_MUTE,
                                                                               HDA_VERB_SET_AMP_GAIN_MUTE_PAYLOAD(c, offset, FALSE, TRUE, TRUE, TRUE, FALSE)), &Response);
          if (EFI_ERROR(Status))
            return Status;
        } else {
          Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_AMP_GAIN_MUTE,
                                                                               HDA_VERB_SET_AMP_GAIN_MUTE_PAYLOAD(c, 0, TRUE, TRUE, TRUE, TRUE, FALSE)), &Response);
          if (EFI_ERROR(Status))
            return Status;
        }
      }
    }
    
    // If there is more than one connection, select our upstream.
    if (HdaWidget->ConnectionCount > 1) {
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_CONN_SELECT_CONTROL,
                                                                           HdaWidget->UpstreamIndex), &Response);
      if (EFI_ERROR(Status))
        return Status;
    }
    
    // If Output, set up stream.
    if (HdaWidget->Type == HDA_WIDGET_TYPE_OUTPUT) {
      DEBUG((DEBUG_INFO, "Widget @ 0x%X output\n", HdaWidget->NodeId));
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_CONVERTER_FORMAT,
                                                                           StreamFormat), &Response);
      if (EFI_ERROR(Status))
        return Status;
      Status = HdaIo->SendCommand(HdaIo, HdaWidget->NodeId, HDA_CODEC_VERB(HDA_VERB_SET_CONVERTER_STREAM_CHANNEL,
                                                                           HDA_VERB_SET_CONVERTER_STREAM_PAYLOAD(0, StreamId)), &Response);
      if (EFI_ERROR(Status))
        return Status;
    }
    
    // Move to upstream widget.
    HdaWidget = HdaWidget->UpstreamWidget;
  }
  return EFI_SUCCESS;
}

VOID
EFIAPI
HdaCodecCleanup(
                IN HDA_CODEC_DEV *HdaCodecDev)
{
  //    DEBUG((DEBUG_INFO, "HdaCodecCleanup(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  HDA_FUNC_GROUP *HdaFuncGroup;
  HDA_WIDGET_DEV *HdaWidget;
  
  // If codec is already clear, we are done.
  if (HdaCodecDev == NULL)
    return;
  
  // Clean HDA Codec Info protocol.
  if (HdaCodecDev->HdaCodecInfoData != NULL) {
    // Uninstall protocol.
    DEBUG((DEBUG_INFO, "HdaCodecCleanup(): clean Hda Codec Info\n"));
    Status = gBS->UninstallProtocolInterface(HdaCodecDev->ControllerHandle,
                                             &gEfiHdaCodecInfoProtocolGuid, &HdaCodecDev->HdaCodecInfoData->HdaCodecInfo);
    ASSERT_EFI_ERROR(Status);
    
    // Free data.
    FreePool(HdaCodecDev->HdaCodecInfoData);
  }
  
  // Clean Audio I/O protocol.
  if (HdaCodecDev->AudioIoData != NULL) {
    // Uninstall protocol.
    DEBUG((DEBUG_INFO, "HdaCodecCleanup(): clean Audio I/O\n"));
    Status = gBS->UninstallProtocolInterface(HdaCodecDev->ControllerHandle,
                                             &gEfiAudioIoProtocolGuid, &HdaCodecDev->AudioIoData->AudioIo);
    ASSERT_EFI_ERROR(Status);
    
    // Free data.
    FreePool(HdaCodecDev->AudioIoData);
  }
  
  // Clean up input and output port arrays.
  if (HdaCodecDev->OutputPorts != NULL)
    FreePool(HdaCodecDev->OutputPorts);
  if (HdaCodecDev->InputPorts != NULL)
    FreePool(HdaCodecDev->InputPorts);
  
  // Clean function groups.
  if (HdaCodecDev->FuncGroups != NULL) {
    // Clean each function group.
    for (UINT8 f = 0; f < HdaCodecDev->FuncGroupsCount; f++) {
      HdaFuncGroup = HdaCodecDev->FuncGroups + f;
      
      // Clean widgets in function group.
      if (HdaFuncGroup->Widgets != NULL) {
        for (UINT8 w = 0; w < HdaFuncGroup->WidgetsCount; w++) {
          HdaWidget = HdaFuncGroup->Widgets + w;
          
          // Clean input amp default arrays.
          if (HdaWidget->AmpInLeftDefaultGainMute != NULL)
            FreePool(HdaWidget->AmpInLeftDefaultGainMute);
          if (HdaWidget->AmpInRightDefaultGainMute != NULL)
            FreePool(HdaWidget->AmpInRightDefaultGainMute);
          
          // Clean connections array.
          if (HdaWidget->WidgetConnections != NULL)
            FreePool(HdaWidget->WidgetConnections);
          if (HdaWidget->Connections != NULL)
            FreePool(HdaWidget->Connections);
        }
        
        // Free widgets array.
        FreePool(HdaFuncGroup->Widgets);
      }
    }
    
    // Free function group array.
    FreePool(HdaCodecDev->FuncGroups);
  }
  
  // Free codec device.
  gBS->UninstallProtocolInterface(HdaCodecDev->ControllerHandle,
                                  &gEfiCallerIdGuid, HdaCodecDev);
  FreePool(HdaCodecDev);
}

EFI_STATUS
EFIAPI
HdaCodecDriverBindingSupported(
                               IN EFI_DRIVER_BINDING_PROTOCOL *This,
                               IN EFI_HANDLE ControllerHandle,
                               IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL)
{
  
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo;
  UINT8 CodecAddress;
  
  // Attempt to open the HDA codec protocol. If it can be opened, we can support it.
  Status = gBS->OpenProtocol(ControllerHandle, &gEfiHdaIoProtocolGuid, (VOID**)&HdaIo,
                             This->DriverBindingHandle, ControllerHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);
  if (EFI_ERROR(Status))
    return Status;
  
  // Get address of codec.
  Status = HdaIo->GetAddress(HdaIo, &CodecAddress);
  if (EFI_ERROR(Status))
    goto CLOSE_CODEC;
  
  // Codec can be supported.
  DEBUG((DEBUG_INFO, "HdaCodecDriverBindingSupported(): attaching to codec 0x%X\n", CodecAddress));
  Status = EFI_SUCCESS;
  
CLOSE_CODEC:
  // Close protocol.
  gBS->CloseProtocol(ControllerHandle, &gEfiHdaIoProtocolGuid, This->DriverBindingHandle, ControllerHandle);
  return Status;
}

EFI_STATUS
EFIAPI
HdaCodecDriverBindingStart(
                           IN EFI_DRIVER_BINDING_PROTOCOL *This,
                           IN EFI_HANDLE ControllerHandle,
                           IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL)
{
  //    DEBUG((DEBUG_INFO, "HdaCodecDriverBindingStart(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  EFI_HDA_IO_PROTOCOL *HdaIo;
  EFI_DEVICE_PATH_PROTOCOL *HdaCodecDevicePath;
  HDA_CODEC_DEV *HdaCodecDev;
  
  // Open HDA I/O protocol.
  Status = gBS->OpenProtocol(ControllerHandle, &gEfiHdaIoProtocolGuid, (VOID**)&HdaIo,
                             This->DriverBindingHandle, ControllerHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);
  if (EFI_ERROR(Status))
    return Status;
  
  // Open Device Path protocol.
  Status = gBS->OpenProtocol(ControllerHandle, &gEfiDevicePathProtocolGuid, (VOID**)&HdaCodecDevicePath,
                             This->DriverBindingHandle, ControllerHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);
  if (EFI_ERROR(Status))
    goto CLOSE_CODEC;
  // Allocate codec device.
  HdaCodecDev = AllocateZeroPool(sizeof(HDA_CODEC_DEV));
  if (HdaCodecDev == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto CLOSE_CODEC;
  }
  
  // Fill codec device data.
  HdaCodecDev->Signature = HDA_CODEC_PRIVATE_DATA_SIGNATURE;
  HdaCodecDev->HdaIo = HdaIo;
  HdaCodecDev->DevicePath = HdaCodecDevicePath;
  HdaCodecDev->ControllerHandle = ControllerHandle;
  // Probe codec.
  Status = HdaCodecProbeCodec(HdaCodecDev);
  if (EFI_ERROR(Status))
    goto FREE_CODEC;
  
  // Get ports.
  Status = HdaCodecParsePorts(HdaCodecDev);
  if (EFI_ERROR(Status))
    goto FREE_CODEC;
  
  // Publish protocols.
  Status = HdaCodecInstallProtocols(HdaCodecDev);
  ASSERT_EFI_ERROR(Status);
  if (EFI_ERROR(Status))
    goto FREE_CODEC;
  
  // Success.
  return EFI_SUCCESS;
  
FREE_CODEC:
  // Cleanup codec.
  HdaCodecCleanup(HdaCodecDev);
  
CLOSE_CODEC:
  // Close protocols.
  gBS->CloseProtocol(ControllerHandle, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, ControllerHandle);
  gBS->CloseProtocol(ControllerHandle, &gEfiHdaIoProtocolGuid, This->DriverBindingHandle, ControllerHandle);
  return Status;
}

EFI_STATUS
EFIAPI
HdaCodecDriverBindingStop(
                          IN EFI_DRIVER_BINDING_PROTOCOL *This,
                          IN EFI_HANDLE ControllerHandle,
                          IN UINTN NumberOfChildren,
                          IN EFI_HANDLE *ChildHandleBuffer OPTIONAL)
{
  //    DEBUG((DEBUG_INFO, "HdaCodecDriverBindingStop(): start\n"));
  
  // Create variables.
  EFI_STATUS Status;
  HDA_CODEC_DEV *HdaCodecDev;
  
  // Get codec device.
  Status = gBS->OpenProtocol(ControllerHandle, &gEfiCallerIdGuid, (VOID**)&HdaCodecDev,
                             This->DriverBindingHandle, ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
  if (!(EFI_ERROR(Status))) {
    // Ensure codec device is valid.
    if (HdaCodecDev->Signature != HDA_CODEC_PRIVATE_DATA_SIGNATURE)
      return EFI_INVALID_PARAMETER;
    
    // Cleanup codec.
    HdaCodecCleanup(HdaCodecDev);
  }
  
  // Close protocols.
  gBS->CloseProtocol(ControllerHandle, &gEfiDevicePathProtocolGuid, This->DriverBindingHandle, ControllerHandle);
  gBS->CloseProtocol(ControllerHandle, &gEfiHdaIoProtocolGuid, This->DriverBindingHandle, ControllerHandle);
  return EFI_SUCCESS;
}

